﻿/*
 * Copyright 2010 Michael Georgoulopoulos
 * 
 * This file is part of Mordor - Source file comparing program.
 * 
 * Mordor is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Mordor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Mordor.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Contact: info@convexgames.com
 * Website: http://www.convexgames.com
 */

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

namespace Mordor
{
    public class SourceFilter
    {
        public SourceFilter()
        {
        }

        string filtered;
        bool ccomment;
        bool cppcomment;
        bool separated;
        bool inside_string;
        string statement;

        bool White(char c)
	    {
		    if (c==' ' || c=='\t' || c==0x0A || c==0x0D) return true;
		    return false;
	    }

	    bool Sep(char c)
	    {
		    if (c=='{' || c=='}' || c=='[' || c==']' || 
			    c=='(' || c==')' || c==':' || c==';' || c=='!' ||
			    c==',' || c=='.' || c=='<' || c=='>' || c=='?') {
				    return true;
		    }
    		
		    return false;
	    }

	    string Str(string s, int size, int max_size)
	    {
		    string ret = "";
            for (int i = 0; i < size && i < max_size; i++)
            {
                ret += s[i];
            }
		    return ret;
	    }

	    int Process(string s, int size)
	    {
		    // end of comments
		    if (ccomment && size > 1) {
			    if (s[0] == '*' && s[1] == '/') {
				    ccomment = false;
				    separated = true;
				    return 2;
			    }
			    return 1;
		    }

		    if (cppcomment) {
			    if (s[0] == 0x0A || s[0] == 0x0D) {
				    cppcomment = false;
				    separated = true;
				    return 1;
			    }
			    return 1;
		    }

		    if (inside_string) {
			    if (s[0] == '\"') {
				    inside_string = false;
				    separated = true;
				    return 1;
			    }
			    return 1;
		    }


		    // comments
		    if (size > 1 && s[0] == '/' && s[1] == '*') {
			    ccomment = true;
			    return 2;
		    }

		    if (size > 1 && s[0] == '/' && s[1] == '/') {
			    cppcomment = true;
			    return 2;
		    }

		    if (s[0] == '\"') {
			    inside_string = true;
			    return 1;
		    }

		    // control statements

            int ret;

            if ( (ret = CStatement(s, size, "if", "i")) > 0) return ret;
            if ( (ret = CStatement(s, size, "else", "e")) > 0) return ret;
            if ( (ret = CStatement(s, size, "for", "f")) > 0) return ret;
            if ( (ret = CStatement(s, size, "do", "d")) > 0) return ret;
            if ( (ret = CStatement(s, size, "while", "w")) > 0) return ret;
            if ( (ret = CStatement(s, size, "switch", "s")) > 0) return ret;
            if ( (ret = CStatement(s, size, "case", "c")) > 0) return ret;
            if ( (ret = CStatement(s, size, "break", "b")) > 0) return ret;
            if ( (ret = CStatement(s, size, "continue", "C")) > 0) return ret;

    	    // separator sequences
		    if (Str(s, 2, size) == "->") {
			    filtered += statement;
			    statement = "";
			    filtered += "->";
			    separated = true;
			    return 2;
		    }

		    if (Str(s, 2, size) == "::") {
			    filtered += statement;
			    statement = "";
			    filtered += "::";
			    separated = true;
			    return 2;
		    }

		    // separator characters
		    if (Sep(s[0])) {
			    filtered += statement;
			    statement = "";
			    filtered += s[0];
			    return 1;
		    }

		    if (White(s[0])) {
			    filtered += statement;
			    statement = "";
			    return 1;
		    }

		    //ignore the rest
		    statement = "";
		    separated = false;
		    return 1;
	    }

        private int CStatement(string s, int size, string x, string y)
        {
            if (separated && Str(s, x.Length, size) == x)
            {
			    statement = y;
			    return x.Length;
		    }
            return 0;
        }

        public string Filter(string source)
	    {
		    filtered = "";
		    ccomment = cppcomment = false;
		    inside_string = false;
		    separated = false;
		    statement = "";
            int remaining_size = source.Length;
		    
		    while (remaining_size > 0)
		    {
			    int chars_processed = Process(source, remaining_size);
			    remaining_size -= chars_processed;
                source = source.Substring(chars_processed);
		    }
		    return filtered;
	    }
    }

    public class SourceFile
    {
        public SourceFile(string filename)
        {
            SourceFilter filter = new SourceFilter();
            StreamReader reader = new StreamReader(filename);
            this.filename = filename;

            try
            {
                filtered = filter.Filter(reader.ReadToEnd());
            }
            catch
            {
                // empty file
            }
            finally
            {
                reader.Close();
            }
        }

        public string filename;
        public string filtered;
    }

    public class Pair
    {
        public string source_a;
        public string source_b;

        public int score;
        public float similarity;

        public Pair(SourceFile file1, SourceFile file2)
        {
            source_a = file1.filename.Substring(file1.filename.LastIndexOf('\\')+1);
            source_b = file2.filename.Substring(file2.filename.LastIndexOf('\\')+1);

            score = Implementation.Levenshtein(file1.filtered, file2.filtered);
            int largest = file1.filtered.Length;
            if (file2.filtered.Length > largest)
                largest = file2.filtered.Length;

            similarity = 0.0f;
            if (largest != 0)
                similarity = 1.0f - score / (float)largest;
            
        }
    }

    public class Implementation
    {
        public Implementation()
        {
            pairs = new List<Pair>();
            sources = new List<SourceFile>();
        }

        public void Clear()
        {
            pairs.Clear();
        }
      
        public void Parse(string[] filenames)
        {
            if (filenames.Length > 0)
            {
                for (int i = 0; i < filenames.Length; i++)
                {
                    sources.Add(new SourceFile(filenames[i]));
                    
                }

                for (int i = 0; i < sources.Count; i++)
                {
                    for (int j = i + 1; j < sources.Count; j++)
                    {
                        pairs.Add(new Pair(sources[i], sources[j]));
                    }
                }
                sources.Clear();
            }
        }

        public static int Levenshtein(string s, string t)
        {
            int m = s.Length;
            int n = t.Length;

            int M = m + 1;

            int[] d = new int[(m + 1) * (n + 1)];

            for (int i = 0; i <= m; i++)
                d[i + 0 * M] = i; // deletion
            for (int j = 0; j <= n; j++)
                d[0 + j * M] = j; // insertion

            for (int j = 1; j <= n; j++)
            {
                for (int i = 1; i <= m; i++)
                {
                    if (s[i - 1] == t[j - 1])
                    {
                        d[i + j * M] = d[(i - 1) + (j - 1) * M];
                    }
                    else
                    {
                        d[i + j * M] = Math.Min
                            (
                            d[(i - 1) + j * M] + 1,  // deletion
                            Math.Min (d[i + (j - 1) * M] + 1,  // insertion
                            d[(i - 1) + (j - 1) * M] + 1 // substitution
                            ));
                    }
                }
            }

            int distance = d[m + n * M];

            return distance;
        }

        public List<Pair> pairs;
        public List<SourceFile> sources;

    }
}
